package NW_MC;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import NWPackage.Calculations;
import NWPackage.Cell;
import NWPackage.MyThread;
import NWPackage.PairPreferdCandidateCell;
import NWPackage.PairPreferdCandidateMatrix;
import NWPackage.ProbabilitiesMatrix;
import NWPackage.ProbabilityCell;
import NWPackage.Protocol;
import NWPackage.QueryFinderAlg;
import NWPackage.WeightedEntropyMatrix;
import Readers.InputsReader;
//import Readers.StaticInputs;
//import Readers.StaticInputs3cands;
//import Writers.MsgLog;
import Writers.MsgLog;

public class MonteCarlo implements QueryFinderAlg{
	
	private MyThread t1;
	private MyThread t2;
	private Random rand;  //picks between cells with equal minimal entropy
	private Random rand2;  //finds a random candidate winner
	private Random rand3; //tie breaks between list of cands with same max votes, found in MC process
	private int random_seed;  //used for the thread
	
	public MonteCarlo(int seed) {
		rand = new Random(seed);
		rand2 = new Random(seed+1);
		random_seed = seed+2;
		rand3 = new Random (seed + 3);
	}
	
	public MonteCarlo( ) {
		rand = new Random( );
		rand2 = new Random();
		random_seed = 1;
		rand3 = new Random ();
	}
	
	public Cell FindNextQuery(ProbabilitiesMatrix probMatrix,PairPreferdCandidateMatrix pairsPreferredMatrix, List<String> optionalWinners, Protocol protocol) throws Exception
	{
		double maxIGain = Double.NEGATIVE_INFINITY;
		//Cell cell = null;
		//this list will contain all the tie-min-entropy cells
		List<Cell> minimumFoundCells = new ArrayList<Cell>();
		
		//initial the weighted entropy matrix:
		WeightedEntropyMatrix entropyMatrix = new WeightedEntropyMatrix();
		entropyMatrix.initial();
		
		//calc the "real" Entropy for this round
		double fullEntropy = CalcFullEntropy(probMatrix,optionalWinners);
		
		//for each "voter Pair preferred" (voter and Pair of Candidates) calculate the Weighted Entropy:
		// (i'm looping on PairPreferredMatrix in purpose because its contains only the relevant Cells)  
		
		for (int i = 0; i< pairsPreferredMatrix.getLength(); i++) {
			PairPreferdCandidateCell entropyCell =  pairsPreferredMatrix.getCell(i);
			if (entropyCell.getPrefered_C() == null && entropyCell.isExistIn(optionalWinners)){
				//System.out.println("Start Calculating WeightedEntropy for Cell: " + i + " /"+pairsPreferredMatrix.getLength() );
				Object[] entropyInfo  = CalcWeightedEntropy(pairsPreferredMatrix.getCell(i),probMatrix,optionalWinners,fullEntropy);
				double weightedEntropy = (Double)entropyInfo[0]; //Information GAIN
				//insert the Weighted Entropy into the Entropy Matrix
				entropyMatrix.setEntropyInfoInCell(i, weightedEntropy, (String)entropyInfo[1]);
				
				//if you found new minimum entropy - remove all previous found
				//cells and save the new cell found
				if (InputsReader.IGUsing ==  false){
					if (weightedEntropy != Double.NEGATIVE_INFINITY)
						minimumFoundCells.add(pairsPreferredMatrix.getCell(i).setAlgPreffered((String)entropyInfo[1]));
				}
				else
				{
					if (weightedEntropy > maxIGain)
					{
						maxIGain = weightedEntropy;
						minimumFoundCells.clear();
						Cell c = pairsPreferredMatrix.getCell(i).setAlgPreffered((String)entropyInfo[1]);
						minimumFoundCells.clear();
						minimumFoundCells.add(c);
					}
					//else if found cell with equal entropy - add it to the list 
					else if (weightedEntropy == maxIGain)
					{
						minimumFoundCells.add(pairsPreferredMatrix.getCell(i).setAlgPreffered((String)entropyInfo[1]));
					}
				}
			}
		}
		
		//now pick randomly one cell from all min-entropy-cells founded
		//Return Query (as Cell) with the lowest Entropy, Return NULL if no Query found
		return RandomCellFromList(minimumFoundCells);
		
	}


	private Cell RandomCellFromList(List<Cell> minimumFoundCells) throws Exception{
		if (minimumFoundCells.size() == 0){
			System.out.println("Error in MONTECARLO: no query found!!" );
			//throw new Exception ("no query found");
			return null;
		}
		if (minimumFoundCells.size() == 1){
			return minimumFoundCells.get(0);
		}
		//random number between 0 and size() and return the cell at that index
		int randomIndex = rand.nextInt(minimumFoundCells.size()-1);
		
		return minimumFoundCells.get(randomIndex);
	}
	
	 

	/***
	 * calc full entropy - later will help to calc the "Info-Gain"
	 * @param probMatrix
	 * @param optionalWinners
	 * @return fullEntropy
	 * @throws Exception 
	 */
	protected double CalcFullEntropy(ProbabilitiesMatrix probMatrix,List<String> optionalWinners) throws Exception {
		double fullEntropy = 0.0;
		fullEntropy = CalcAssumptionEntropy(probMatrix,null,optionalWinners,0.0);
		
		//bug fix for -0.0 answer
		if (fullEntropy == -0.0)
			fullEntropy = 0.0;
		return fullEntropy;
	}

	protected Object[] CalcWeightedEntropy(PairPreferdCandidateCell cell, ProbabilitiesMatrix probMatrix, List<String> optionalWinners, double fullEntropy) throws Exception {
		//create two Assumptions cells (A>B and A<B)
		//MsgLog.write("Create the Assumptions cells");
		String[] candidates = cell.getCandidates();
		PairPreferdCandidateCell bigCellAssumption = new PairPreferdCandidateCell(cell.getAgentName(),candidates[0],candidates[1], candidates[0]);
		PairPreferdCandidateCell smallCellAssumption = new PairPreferdCandidateCell(cell.getAgentName(),candidates[0],candidates[1], candidates[1]);
		
		//make temp Normalize probability Matrix for each Assumption:
		//MsgLog.write("Normalize each matrix by the Assumption");
		ProbabilitiesMatrix bigNormalizeProbabilityMatrix = probMatrix.normalize(bigCellAssumption);
		ProbabilitiesMatrix smallNormalizeProbabilityMatrix = probMatrix.normalize(smallCellAssumption);
		
		//initial statistics
		double weight1 = 0.0;
		double weight2 = 0.0;
		
		
		
		//calc twice for A<B and A>B Assumptions the Weighted Entropy:
		//MsgLog.write("Calculating the Entropy for each assumption");
		t1 = new MyThread("big",bigNormalizeProbabilityMatrix,bigCellAssumption,optionalWinners,fullEntropy, random_seed);
		t2 = new MyThread("small",smallNormalizeProbabilityMatrix,smallCellAssumption,optionalWinners,fullEntropy, random_seed);

	//	weight1 = CalcAssumptionEntropy(bigNormalizeProbabilityMatrix,bigCellAssumption,optionalWinners,fullEntropy);
	//	weight2 = CalcAssumptionEntropy(smallNormalizeProbabilityMatrix,smallCellAssumption,optionalWinners,fullEntropy);
		
		try {
			t1.t.join();
			t2.t.join();			
		} catch (InterruptedException e) {
			System.out.println("Main thread Interrupted");
			e.printStackTrace();
		}
		//bug fix for -0.0 answer
		if (t1.weight == -0.0)
			weight1 = 0;
		else
			weight1 = t1.weight;
		if (t2.weight == -0.0)
			weight2 = 0;
		else
			weight2 = t2.weight;
		t1 = null;
		t2=null;
		//-----------------------------------------//
		//for Debug//
		double initialAssumptionProbA = bigNormalizeProbabilityMatrix.getAgentTotalProbability(cell.getAgentName());
		double initialAssumptionProbB = smallNormalizeProbabilityMatrix.getAgentTotalProbability(cell.getAgentName());
		String needToWin = (initialAssumptionProbA > initialAssumptionProbB) ? bigCellAssumption.getPrefered_C() : smallCellAssumption.getPrefered_C();
		//-----------------------------------------//
		Object[] o = new Object[2];
		o[0] = (double)(weight1+weight2);
		o[1] = needToWin;

		return o;
		//return weight1+weight2;
	}
	
	protected double CalcAssumptionEntropy(ProbabilitiesMatrix normalizeProbabilityMatrix,Cell assumption, 
			List<String> optionalWinners, double fullEntropy) throws Exception {
		
		MCEntropyMatrix winnersStatistics = new MCEntropyMatrix(optionalWinners);
		//Run X times The Random Number's Winner Process:
		for (int j = 0; j < InputsReader.getMonteCarloRandomProcess(); j++) {
			String candidateWinner="";
			try{
				candidateWinner = findRandomCandidateWinner(normalizeProbabilityMatrix,optionalWinners);
			}
			catch(Exception ex){
				//MsgLog.write("problem " +ex.getMessage());
				//ex.printStackTrace();
				System.out.println("prob1");
			}
			//after each iteration save the winning to the Statistics:
			try {
				winnersStatistics.increaseCandidateWinner(candidateWinner);
			} 
			catch (Exception e) {
				//MsgLog.write("cannot increase the Candidate: " + candidateWinner );
				//e.printStackTrace();
				System.out.println("prob2");
			}
		}//for
	//	System.out.println( "(assumption winner): winner = " + winnersStatistics.getWinner());
		//after all the X-MC iterations - calc the entropy of each Candidate:
		double assumptionProbability = 1.0;;
		//if assumption is NULL then we are calculating the full real Entropy
		if (assumption == null){
			assumptionProbability = 1.0;			
		}
		//else we are calculating some query's Entropy
		else{
			try{
			assumptionProbability = normalizeProbabilityMatrix.getAgentTotalProbability(assumption.getAgentName());
			}
			catch (Exception e){
				System.out.println("prob3 - MonteCarlo");
			}
		}
		
		//log the winner statistics:
		//MsgLog.write_to_init("MonteCarlo: logging winner statistics. assumed_winner=" + winnersStatistics.getWinner());
		MsgLog.write_to_init("assumed_winner=" + winnersStatistics.getWinner() + " " + winnersStatistics.stringIt());
		
		
		//return entropy for this query by huristics:
		switch(InputsReader.getHuristic())
		{
			case 1: 
				// only winners entropies
				return Huristics.AllWinners(assumption, winnersStatistics, assumptionProbability, fullEntropy);
			case 2:
				//winner & Loser
				return Huristics.WinnerVSLoser(assumption, winnersStatistics, assumptionProbability, fullEntropy);
			case 3:
				//winner & second-winner
				return Huristics.WinnerVSSecond(assumption, winnersStatistics, assumptionProbability, fullEntropy);
			case 6:
				//mypoic
				return Huristics.Myopic(assumption, winnersStatistics, assumptionProbability);
			case 7:
				//myopic winner
				return Huristics.MyopicWinner(assumption, winnersStatistics, assumptionProbability);
			case 8:
				//pure winner
				return Huristics.PureWinner(assumption, winnersStatistics, assumptionProbability);
			case 9:
				//pure preferred winner
				return Huristics.PurePreferredWinner(assumption, winnersStatistics, assumptionProbability);
			default:
				//all queries - Entropy Only
				return Huristics.allQueries(winnersStatistics, assumptionProbability, fullEntropy);
		}

			
		
		
	}
	
	/*for each agent use a lottery to get the permutation */
	private String findRandomCandidateWinner(ProbabilitiesMatrix probMatrix,List<String> optionalWinners) {
		RandomMatrix rMatrix = new RandomMatrix(optionalWinners);	
		/*//FOR DEBUG ONLY
		double randomProb1 = 0.1;
		double randomProb2 = 0.2;
		ProbabilityCell probability1 = probMatrix.getProbability("V1",randomProb1);
		rMatrix.setCell(probability1);
		ProbabilityCell probability2 = probMatrix.getProbability("V2",randomProb2);
		rMatrix.setCell(probability2);
		*/		
		//for each agent, get random Vote:
		String[] agents = InputsReader.getAgents();
		for (String  agent : agents) {
			double randomProb = rand2.nextDouble(); // V1 = 0.2157 V2 = 0.6027 V3 = 0.4427
			//System.out.println("rnadomprob="+ randomProb);
			ProbabilityCell probability = new ProbabilityCell();
			try{
				//get a permutation for that certain agent
				probability = probMatrix.getProbability(agent,randomProb);
				String s = probability.printCell();
				//save the vote in the randomMatrix
				rMatrix.setCell(probability);
			}//try
			catch (Exception e) {
				//MsgLog.write("error in prob matrix - agent " + agent + "not found");
				System.out.println("Exception in prob matrix - agent " + agent + "not found");
			}
		}//for		
		//get the candidate with max value
		List<String> winnerList =  rMatrix.getWinnerCandidate();
		int idx = rand3.nextInt(winnerList.size());
		String winner = winnerList.get(idx);
		return winner;
	}

}
